package org.apache.ibatis.common.parsing;

import com.sun.javafx.runtime.eula.Eula;
import org.w3c.dom.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * @Name XNode
 * @Description: Node 包装类
 * 描述信息：该类作为 “org.w3c.dom.Node” 的包装类，用来表示 DOM 中的节点信息。该类在 Node 的基础上提取和补充了几个属性。
 * @Version v1.0.0
 * @Author: lichao
 * @Date: 2024-05-16 14:29:38
 **/
public class XNode {
    /**
     * XML 解析器
     * 说明：XNode 依赖 XML 解析器，具备了解析 XML 节点的能力。即，XNode 具备自解析能力。
     * 正是得益于 XNode 类的自解析特性，它本身提供了以下 "eval*" 方法，从而能够解析自身节点内的信息。
     */
    private final XPathParser xPathParser;
    // MyBatis 配置文件中的 properties 信息
    private final Properties variables;
    // XML 节点
    private final Node node;
    // XML 节点名称
    private final String name;
    // XML 节点体
    private final String body;
    // XML 节点内容
    private final Properties attributes;

    public XNode(XPathParser xPathParser, Properties variables, Node node) {
        this.xPathParser = xPathParser;
        this.variables = variables;
        this.node = node;
        this.name = node.getNodeName();
        this.body = parseBody(node);
        this.attributes = parseAttributes(node);
    }

    public XNode newXNode(Node node) {
        return new XNode(xPathParser, variables, node);
    }

    public XNode getParent() {
        Node parent = node.getParentNode();
        if (!(parent instanceof Element)) return null;
        return new XNode(xPathParser, variables, parent);
    }

    public String getPath() {
        StringBuffer buffer = new StringBuffer();
        Node current = node;
        while (current instanceof Element) {
            if (current != node) buffer.insert(0, "/");
            buffer.insert(0, current.getNodeName());
            current = current.getParentNode();
        }
        return buffer.toString();
    }

    public String getValueBasedIdentifier() {
        StringBuffer buffer = new StringBuffer();
        XNode current = this;
        while (current != null) {
            if (current != this) buffer.insert(0, "_");
            String value = current.getStringAttribute("value", current.getStringAttribute("property", null));
            if (value != null) {
                value = value.replace(".", "_");
                buffer.insert(0, "]");
                buffer.insert(0, value);
                buffer.insert(0, "[");
            }
            buffer.insert(0, current.getName());
            current = current.getParent();
        }
        return buffer.toString();
    }

    public List<XNode> getChildren() {
        List<XNode> children = new ArrayList<>();
        NodeList nodeList = node.getChildNodes();
        if (nodeList != null) {
            for (int i = 0; i < nodeList.getLength(); i++) {
                Node child = nodeList.item(i);
                if (child.getNodeType() == Node.ELEMENT_NODE) {
                    children.add(new XNode(xPathParser, variables, child));
                }
            }
        }
        return children;
    }

    public Properties getChildrenProperties() {
        Properties properties = new Properties();
        for (XNode child : getChildren()) {
            String name = child.getStringAttribute("name");
            String value = child.getStringAttribute("value");
            if (name != null && value != null) {
                properties.setProperty(name, value);
            }
        }
        return properties;
    }

    public Node getNode() {
        return node;
    }

    public String getName() {
        return name;
    }

    public String getStringBody() {
        return getStringBody(null);
    }

    public String getStringBody(String def) {
        if (body == null) return def;
        return body;
    }

    public Boolean getBooleanBody() {
        return getBooleanBody(null);
    }

    public Boolean getBooleanBody(Boolean def) {
        if (body == null) return def;
        return Boolean.valueOf(body);
    }

    public Integer getIntegerBody() {
        return getIntegerBody(null);
    }

    public Integer getIntegerBody(Integer def) {
        if (body == null) return def;
        return Integer.parseInt(body);
    }

    public Long getLongBody() {
        return getLongBody(null);
    }

    public Long getLongBody(Long def) {
        if (body == null) return def;
        return Long.parseLong(body);
    }

    public Float getFloatBody() {
        return getFloatBody(null);
    }

    public Float getFloatBody(Float def) {
        if (body == null) return def;
        return Float.parseFloat(body);
    }

    public Double getDoubleBody() {
        return getDoubleBody(null);
    }

    public Double getDoubleBody(Double def) {
        if (body == null) return def;
        return Double.parseDouble(body);
    }

    public <T extends Enum<T>> T getEnumAttribute(Class<T> enumType, String name) {
        return getEnumAttribute(enumType, name, null);
    }

    public <T extends Enum<T>> T getEnumAttribute(Class<T> enumType, String name, T def) {
        String value = getStringAttribute(name);
        if (value == null) return def;
        return Enum.valueOf(enumType, value);
    }

    public String getStringAttribute(String name) {
        return getStringAttribute(name, null);
    }

    public String getStringAttribute(String name, String def) {
        String value = attributes.getProperty(name);
        if (value == null) return def;
        return value;
    }

    public Boolean getBooleanAttribute(String name) {
        return getBooleanAttribute(name, null);
    }

    public Boolean getBooleanAttribute(String name, Boolean def) {
        String value = attributes.getProperty(name);
        if (value == null) return def;
        return Boolean.valueOf(value);
    }

    public Integer getIntegerAttribute(String name) {
        return getIntegerAttribute(name, null);
    }

    public Integer getIntegerAttribute(String name, Integer def) {
        String value = attributes.getProperty(name);
        if (value == null) return def;
        return Integer.parseInt(value);
    }

    public Long getLongAttribute(String name) {
        return getLongAttribute(name, null);
    }

    public Long getLongAttribute(String name, Long def) {
        String value = attributes.getProperty(name);
        if (value == null) return def;
        return Long.parseLong(value);
    }

    public Float getFloatAttribute(String name) {
        return getFloatAttribute(name, null);
    }

    public Float getFloatAttribute(String name, Float def) {
        String value = attributes.getProperty(name);
        if (value == null) return def;
        return Float.parseFloat(value);
    }

    public Double getDoubleAttribute(String name) {
        return getDoubleAttribute(name, null);
    }

    public Double getDoubleAttribute(String name, Double def) {
        String value = attributes.getProperty(name);
        if (value == null) return def;
        return Double.parseDouble(value);
    }

    public String evalString(String expression) {
        return xPathParser.evalString(node, expression);
    }

    public Boolean evalBoolean(String expression) {
        return xPathParser.evalBoolean(node, expression);
    }

    public Double evalDouble(String expression) {
        return xPathParser.evalDouble(node, expression);
    }

    public XNode evalNode(String expression) {
        return xPathParser.evalNode(node, expression);
    }

    public List<XNode> evalNodes(String expression) {
        return xPathParser.evalNodes(node, expression);
    }

    @Override
    public String toString() {
        StringBuffer buffer = new StringBuffer();
        buffer.append("<");
        buffer.append(name);
        for (Map.Entry<Object, Object> entry : attributes.entrySet()) {
            buffer.append(" ");
            buffer.append(entry.getKey());
            buffer.append("=\"");
            buffer.append(entry.getValue());
            buffer.append("\"");
        }
        List<XNode> children = getChildren();
        if (!children.isEmpty()) {
            buffer.append(">\n");
            for (XNode child : children) buffer.append(child.toString());
            buffer.append("</");
            buffer.append(name);
            buffer.append(">");
        } else if (body != null) {
            buffer.append(">");
            buffer.append(body);
            buffer.append("</");
            buffer.append(name);
            buffer.append(">");
        } else buffer.append("/>");
        buffer.append("\n");
        return buffer.toString();
    }

    private String parseBody(Node node) {
        String data = getBodyData(node);
        if (data == null) {
            NodeList children = node.getChildNodes();
            for (int i = 0; i < children.getLength(); i++) {
                Node child = children.item(i);
                data = getBodyData(child);
                if (data != null) break;
            }
        }
        return data;
    }

    private String getBodyData(Node child) {
        if (child.getNodeType() == Node.CDATA_SECTION_NODE || child.getNodeType() == Node.TEXT_NODE) {
            String data = ((CharacterData) child).getData();
            data = PropertyParser.parse(data, variables);
            return data;
        }
        return null;
    }

    private Properties parseAttributes(Node node) {
        Properties attributes = new Properties();
        NamedNodeMap attributeNodes = node.getAttributes();
        if (attributeNodes != null) {
            for (int i = 0; i < attributeNodes.getLength(); i++) {
                Node attribute = attributeNodes.item(i);
                String value = PropertyParser.parse(attribute.getNodeValue(), variables);
                attributes.put(attribute.getNodeName(), value);
            }
        }
        return attributes;
    }


}

